package jvm

import (
	"encoding/hex"
	"fmt"
	"strconv"

	"github.com/golang/protobuf/proto"
	jvmcommon "hundsun.com/hsl/hschain/contract/jvm/jvm.go/common"
	"hundsun.com/hsl/hschain/contract/jvm/types"
	ecom "hundsun.com/hsl/hschain/executor/common"
	"hundsun.com/hsl/hschain/protos/common"
	pb "hundsun.com/hsl/hschain/protos/execute"
	protosledger "hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/protos/rpc"
	"hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/extend/account"
	storeContract "hundsun.com/hsl/hschain/store/extend/contract"
	"hundsun.com/hsl/hschain/store/extend/ledger"
)

// Query jvm查询接口
func (p *Jvm) Query(ctx *ecom.ExecContent, funcName string, param []byte) (proto.Message, error) {
	switch funcName {
	case types.QueryHistoryEventMethod:
		return p.queryContractEvent(ctx, param)
	case types.QueryContractVersionMethod:
		return p.queryContractVersion(ctx, param)
	case types.QueryContractPureMethod:
		return p.queryContractPure(ctx, param)
	default:
		return nil, fmt.Errorf("%s not implemented", funcName)
	}
}

func (p *Jvm) queryContractVersion(ctx *ecom.ExecContent, param []byte) (proto.Message, error) {
	contractName := string(param)

	ctVerBytes, err := ctx.Get(storeContract.GenContractVersionKey(contractName))
	if err != nil {
		return nil, err
	}

	ctVer := &common.ContractVersion{}
	err = proto.Unmarshal(ctVerBytes, ctVer)
	if err != nil {
		return nil, err
	}

	return ctVer, nil
}

// 查询数据，获取合约历史事件
func (p *Jvm) queryContractEvent(ctx *ecom.ExecContent, param []byte) (proto.Message, error) {
	var result rpc.ContractEvents
	var contractEventInfo = &rpc.ContractEventSubInfo{}
	err := proto.Unmarshal(param, contractEventInfo)
	if err != nil {
		return nil, err
	}

	start, _ := strconv.ParseUint(contractEventInfo.StartBlockNum, 10, 0)
	end, _ := strconv.ParseUint(contractEventInfo.EndBlockNum, 10, 0)

	for blkHeight := start; blkHeight <= end; blkHeight++ {
		blkExecBytes, err := ctx.GetLedger().Get(ledger.GenKey(db.NamespaceLedgerBlockExecResult, strconv.FormatUint(blkHeight, 10)))
		if err != nil {
			continue
		}

		var blkExecResult = &protosledger.BlockExecResult{}
		err = proto.Unmarshal(blkExecBytes, blkExecResult)
		if err != nil {
			return nil, err
		}

		for _, txExecResult := range blkExecResult.TransactionExecResults {
			for _, event := range txExecResult.Receipt.Events {
				if contractEventInfo.Compare(event) {
					result.Results = append(result.Results, event)
				}
			}
		}
	}

	return &result, nil
}

func (p *Jvm) queryContractPure(ctx *ecom.ExecContent, param []byte) (proto.Message, error) {
	var tx = &protosledger.Transaction{}
	err := proto.Unmarshal(param, tx)
	if err != nil {
		return nil, err
	}

	from := tx.GetFrom()
	payload := tx.GetPayload()
	txHash := tx.Hash()
	txLogHash := hex.EncodeToString(txHash)[:6]
	var receipt = &protosledger.TransactionReceipt{
		TxHash:     txHash,
		TxStatus:   protosledger.TransactionReceipt_SUCCESSFUL,
		ContractId: tx.ContractId,
	}

	//交易参数校验
	if from == "" || payload == nil || len(payload) == 0 {
		receipt.TxStatus = protosledger.TransactionReceipt_PAYLOAD_EMPTY
		return receipt, nil
	}

	//只读交易不允许转账
	if tx.Value > 0 {
		log.Errorf("Excute pure tx [%s] failed ,operation transfer not allowed!", txLogHash)
		receipt.TxStatus = protosledger.TransactionReceipt_FAILED_OTHER_REASON
		return receipt, nil
	}
	//验证交易签名
	if !tx.Verify() {
		log.Errorf("Pure tx [%s] signature is invalid!", txLogHash)
		//设置交易执行错误结果
		receipt.TxStatus = protosledger.TransactionReceipt_SIGNATURE_INVALID
		return receipt, nil
	}

	acc := account.NewAccountDB(ctx)
	ctAddr := tx.GetContractAddressByID()
	ctAccount, err := acc.GetAccount(ctAddr)
	if err != nil {
		log.Errorf("get account failed to addr is %s; err :%s", tx.ContractId.Name, err.Error())
		receipt.TxStatus = protosledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
		return receipt, err
	}

	if ctAccount == nil || ctAccount.Type != common.Account_CONTRACT {
		//合约账户不存在
		if ctAccount == nil {
			log.Errorf("Excute pure tx [%s] failed ,Contract account %s is not exist", txLogHash, tx.ContractId.Name)
		} else {
			log.Errorf("Excute pure tx [%s] failed ,Contract account %s type is not contract", txLogHash, tx.ContractId.Name)
		}
		receipt.TxStatus = protosledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
		return receipt, nil
	}

	contractVersion := storeContract.NewContractVesionDB(ctx)
	lastVer, err := contractVersion.GetContractVersion(tx.ContractId.Name)
	if err != nil {
		log.Errorf("Excute pure tx [%s] failed,contract %s version not found!", txLogHash, tx.ContractId.Name)
		receipt.TxStatus = protosledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
		return receipt, nil
	}

	//合约已被停用或注销
	if lastVer.State != common.ContractVersion_NORMAL {
		log.Errorf("Excute pure tx [%s] failed,Contract %s state is not normal!", txLogHash, tx.ContractId.Name)
		receipt.TxStatus = protosledger.TransactionReceipt_CONTRACT_STATE_NOT_NORMAL
		return receipt, nil
	}

	// 校验调用的合约版本
	if !jvmcommon.ContractVersionCompare(tx.ContractId.Version, lastVer.LastVersion) {
		log.Errorf("Excute pure tx [%s] failed,Contract version %s is lower than last version %s",
			txLogHash, tx.ContractId.Version, lastVer.LastVersion)
		receipt.TxStatus = protosledger.TransactionReceipt_CONTRACT_LOST_EFFICACY
		return receipt, nil
	}

	contractAddr := tx.GetContractAddressByID()
	//使用最新区块头
	//currentBlockHeader := ledger.BlockHeader{Number:1}
	groupedTx := &pb.GroupedTransaction{Tx: tx}
	groupedTx.TxExecRst = &protosledger.TransactionExecResult{
		Receipt: receipt,
	}

	receipt.ContractAddress = contractAddr
	receipt.ContractId = tx.ContractId

	err = p.runCallContract(ctx, groupedTx)
	if err != nil {
		receipt.TxStatus = protosledger.TransactionReceipt_CALL_CONTRACT_ERROR
		receipt.CallResult = []byte(err.Error())
		return receipt, err
	}

	receipt.TxStatus = groupedTx.TxExecRst.Receipt.TxStatus
	receipt.Logs = groupedTx.TxExecRst.Receipt.Logs
	receipt.CallResult = groupedTx.TxExecRst.Receipt.CallResult
	return receipt, nil
}
